function MVVM(options) {
  // options是配置对象：属性名固定的对象
  // 实例对象上的属性和方法往往都是$开头命名，避免将来和数据代理的属性冲突
  this.$options = options || {};
  // _xxx 开头的属性是私有属性
  // 原data数据
  var data = (this._data = this.$options.data);
  // 为了其他函数可以访问外面函数的this
  var me = this;

  // 1. 数据代理：将data中的属性代理到this上，可以通过this直接访问数据
  // 作用：为了更加简单、方便访问数据

  // 提取原data数据所有属性名进行遍历
  // 对每一个属性进行数据代理
  Object.keys(data).forEach(function (key) {
    // 调用数据代理的方法处理每一个属性
    me._proxyData(key);
  });

  // this._initComputed();

  // 2. 数据劫持：将data数据中所有属性进行重新定义，定义成响应式的属性
  // 所有响应式数据（data中的属性）都有dep对象
  // 所有模板语法（除了事件指令以外）都有watcher对象，watcher对象中保存更新用户界面的方法updaterFn
  observe(data, this);

  // 3. 模板解析：解析页面中的模板语法（插值语法和指令语法）
  this.$compile = new Compile(options.el || document.body, this);
}

MVVM.prototype = {
  constructor: MVVM,
  $watch: function (key, cb, options) {
    new Watcher(this, key, cb);
  },

  // 数据代理
  _proxyData: function (key, setter, getter) {
    var me = this;
    setter =
      setter ||
      // 通过Object.defineProperty方法给this添加属性，还设置属性get/set。（将来可以通过this直接访问数据了）
      // 访问数据代理的属性的时候，触发get方法，实际读取的是原data数据对应属性的值
      // 设置数据代理的属性的时候，触发set方法，实际设置的是原data数据对应属性的值
      Object.defineProperty(me, key, {
        configurable: false,
        enumerable: true,
        get: function proxyGetter() {
          return me._data[key];
        },
        set: function proxySetter(newVal) {
          me._data[key] = newVal;
        },
      });
  },

  _initComputed: function () {
    var me = this;
    var computed = this.$options.computed;
    if (typeof computed === "object") {
      Object.keys(computed).forEach(function (key) {
        Object.defineProperty(me, key, {
          get: typeof computed[key] === "function" ? computed[key] : computed[key].get,
          set: function () {},
        });
      });
    }
  },
};
